package com.ruoyi.file.service.storage.impl.local;

import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.config.ServerConfig;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.file.domain.SysStorageConfig;
import com.ruoyi.file.domain.dto.CompleteMultipartRequest;
import com.ruoyi.file.domain.vo.UploadTokenVO;
import com.ruoyi.file.service.storage.StorageService;
import com.ruoyi.file.utils.FilePathUtils;
import com.ruoyi.file.utils.FileTypeUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 本地文件存储服务实现
 * 使用本地文件系统存储文件，目录模拟桶的概念
 *
 * @author ruoyi
 */
@Slf4j
@Service("localStorageService")
@RequiredArgsConstructor
public class LocalStorageServiceImpl implements StorageService {

    private final ServerConfig serverConfig;

    @Override
    public String uploadFile(InputStream inputStream, String fileName, String contentType, SysStorageConfig config) {
        return uploadFile(inputStream, fileName, contentType, config, null);
    }

    @Override
    public String uploadFile(InputStream inputStream, String fileName, String contentType, SysStorageConfig config, String targetPath) {
        try {
            // 使用 combinePathAndFileName 只拼接路径，不添加时间戳
            String filePath = FilePathUtils.combinePathAndFileName(fileName, targetPath);

            Path fullPath = getFullPath(config, filePath);

            // 确保目录存在
            Files.createDirectories(fullPath.getParent());

            // 保存文件
            Files.copy(inputStream, fullPath, StandardCopyOption.REPLACE_EXISTING);
            return filePath;
        } catch (Exception e) {
            log.error("本地文件上传失败: {}", e.getMessage(), e);
            throw new ServiceException("上传文件失败: " + e.getMessage());
        }
    }

    @Override
    public InputStream downloadFile(String filePath, SysStorageConfig config) {
        try {
            Path fullPath = getFullPath(config, filePath);
            if (!Files.exists(fullPath)) {
                throw new ServiceException("文件不存在: " + filePath);
            }
            return Files.newInputStream(fullPath);
        } catch (Exception e) {
            log.error("本地文件下载失败: {}", e.getMessage(), e);
            throw new ServiceException("下载文件失败: " + e.getMessage());
        }
    }

    @Override
    public boolean deleteFile(String filePath, SysStorageConfig config) {
        try {
            Path fullPath = getFullPath(config, filePath);
            boolean deleted = Files.deleteIfExists(fullPath);
            if (deleted) {
            }
            return deleted;
        } catch (Exception e) {
            log.error("本地文件删除失败: {}", e.getMessage(), e);
            return false;
        }
    }

    @Override
    public String getFileUrl(String filePath, SysStorageConfig config) {
        if (StringUtils.isNotBlank(config.getDomain())) {
            // 本地存储使用自定义域名 + /profile/ 前缀
            return config.getDomain().replaceAll("/$", "") + "/profile/" + config.getBucketName() + "/" + filePath;
        } else {
            // 使用静态资源映射路径，通过 /profile/ 访问
            return serverConfig.getUrl() + "/profile/" + config.getBucketName() + "/" + filePath;
        }
    }

    @Override
    public String getPresignedUploadUrl(String filePath, SysStorageConfig config, Duration expiration) {
        // 本地存储不支持预签名URL，返回上传接口路径
        return "/file/upload?bucket=" + config.getBucketName() + "&filePath=" + filePath;
    }

    @Override
    public String getPresignedDownloadUrl(String filePath, SysStorageConfig config, Duration expiration) {
        // 本地存储直接返回文件URL
        return getFileUrl(filePath, config);
    }

    @Override
    public boolean fileExists(String filePath, SysStorageConfig config) {
        try {
            Path fullPath = getFullPath(config, filePath);
            return Files.exists(fullPath);
        } catch (Exception e) {
            log.error("检查本地文件是否存在失败: {}", e.getMessage(), e);
            return false;
        }
    }

    @Override
    public UploadTokenVO generateUploadToken(String fileName, SysStorageConfig config) {
        String filePath = FilePathUtils.generateFilePathWithDate(fileName);

        return UploadTokenVO.builder()
                .uploadUrl("/file/upload") // 本地上传接口
                .endpoint(config.getEndpoint())
                .bucketName(config.getBucketName())
                .region(config.getRegion())
                .filePath(filePath)
                .expireTime(Instant.now().plus(Duration.ofSeconds(config.getTokenExpireTime())).toEpochMilli())
                .maxFileSize(config.getMaxFileSize())
                .allowedTypes(parseAllowedTypes(config.getAllowedFileTypes()))
                .configId(config.getConfigId())
                .build();
    }

    @Override
    public UploadTokenVO generateUploadToken(String fileName, SysStorageConfig config, String dirPath) {
        // 使用 combinePathAndFileName 只拼接路径，不添加时间戳
        String filePath = FilePathUtils.combinePathAndFileName(fileName, dirPath);

        return UploadTokenVO.builder()
                .uploadUrl("/file/upload") // 本地上传接口
                .endpoint(config.getEndpoint())
                .bucketName(config.getBucketName())
                .region(config.getRegion())
                .filePath(filePath)
                .expireTime(Instant.now().plus(Duration.ofSeconds(config.getTokenExpireTime())).toEpochMilli())
                .maxFileSize(config.getMaxFileSize())
                .allowedTypes(parseAllowedTypes(config.getAllowedFileTypes()))
                .configId(config.getConfigId())
                .build();
    }

    @Override
    public boolean createBucket(String bucketName, SysStorageConfig config) {
        try {
            Path bucketPath = getBucketPath(config, bucketName);
            Files.createDirectories(bucketPath);
            return true;
        } catch (Exception e) {
            log.error("创建本地存储目录失败: {}", e.getMessage(), e);
            throw new ServiceException("创建存储目录失败: " + e.getMessage());
        }
    }

    @Override
    public boolean deleteBucket(String bucketName, SysStorageConfig config) {
        try {
            Path bucketPath = getBucketPath(config, bucketName);
            if (Files.exists(bucketPath)) {
                // 检查目录是否为空
                try (var stream = Files.list(bucketPath)) {
                    if (stream.findAny().isPresent()) {
                        throw new ServiceException("目录不为空，无法删除: " + bucketName);
                    }
                }
                Files.delete(bucketPath);
            }
            return true;
        } catch (Exception e) {
            log.error("删除本地存储目录失败: {}", e.getMessage(), e);
            return false;
        }
    }

    @Override
    public boolean bucketExists(String bucketName, SysStorageConfig config) {
        try {
            Path bucketPath = getBucketPath(config, bucketName);
            return Files.exists(bucketPath) && Files.isDirectory(bucketPath);
        } catch (Exception e) {
            log.debug("检查本地存储目录失败: {}", e.getMessage());
            return false;
        }
    }

    @Override
    public List<String> listBuckets(SysStorageConfig config) {
        try {
            Path rootPath = Paths.get(getLocalStorageRootPath());
            if (!Files.exists(rootPath)) {
                Files.createDirectories(rootPath);
                return new ArrayList<>();
            }
            try (var stream = Files.list(rootPath)) {
                return stream
                        .filter(Files::isDirectory)
                        .map(path -> path.getFileName().toString())
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("获取本地存储目录列表失败: {}", e.getMessage(), e);
            return new ArrayList<>();
        }
    }

    @Override
    public boolean setBucketPermission(String bucketName, String permission, SysStorageConfig config) {
        // 本地文件存储不需要权限设置
        log.info("本地文件存储不支持权限设置: {} -> {}", bucketName, permission);
        return true;
    }

    @Override
    public String getBucketPermission(String bucketName, SysStorageConfig config) {
        // 本地文件存储默认公共读
        return "public-read";
    }

    @Override
    public boolean configureBucketCors(String bucketName, SysStorageConfig config) {
        // 本地文件存储不需要CORS配置
        return true;
    }

    @Override
    public String getBucketCors(String bucketName, SysStorageConfig config) {
        // 本地文件存储不需要CORS配置
        return "本地存储无需CORS配置 - 通过服务器代理访问，无跨域问题";
    }

    @Override
    public boolean deleteBucketCors(String bucketName, SysStorageConfig config) {
        return true;
    }

    /**
     * 获取完整文件路径
     */
    private Path getFullPath(SysStorageConfig config, String filePath) {
        return getBucketPath(config, config.getBucketName()).resolve(filePath);
    }

    /**
     * 获取桶目录路径
     */
    private Path getBucketPath(SysStorageConfig config, String bucketName) {
        String rootPath = getLocalStorageRootPath();
        return Paths.get(rootPath, bucketName);
    }

    /**
     * 获取本地存储根目录路径
     */
    private String getLocalStorageRootPath() {
        String configPath = RuoYiConfig.getProfile();
        if (StringUtils.isBlank(configPath)) {
            return System.getProperty("user.dir") + File.separator + "uploads";
        }
        return configPath;
    }


    /**
     * 解析允许的文件类型
     */
    private List<String> parseAllowedTypes(String allowedFileTypes) {
        return FileTypeUtils.parseAllowedTypes(allowedFileTypes);
    }

    // ==================== 统一分片上传接口实现 ====================

    @Override
    public String initMultipartUpload(String fileName, SysStorageConfig config) {
        try {
            // 生成上传ID
            String uploadId = generateUploadId();

            // 创建临时目录（使用配置的本地存储路径）
            String tempDirPath = RuoYiConfig.getProfile() + File.separator + "temp" + File.separator + uploadId;
            Path tempDir = Paths.get(tempDirPath);
            Files.createDirectories(tempDir);

            log.info("初始化本地分片上传成功: fileName={}, uploadId={}, tempDir={}", fileName, uploadId, tempDirPath);
            return uploadId;
        } catch (Exception e) {
            log.error("初始化本地分片上传失败: {}", e.getMessage(), e);
            throw new ServiceException("初始化分片上传失败: " + e.getMessage());
        }
    }

    @Override
    public String getPresignedUploadUrl(String fileName, String uploadId, int partNumber, SysStorageConfig config) {
        // 本地存储返回分片上传的后端接口URL
        String baseUrl = serverConfig.getUrl();
        return baseUrl + "/file/chunk/upload";
    }

    @Override
    public String uploadPart(String fileName, String uploadId, int partNumber, java.io.InputStream inputStream, SysStorageConfig config) {
        try {
            // 临时目录路径
            String tempDirPath = RuoYiConfig.getProfile() + File.separator + "temp" + File.separator + uploadId;
            Path tempDir = Paths.get(tempDirPath);

            // 创建临时目录
            if (!Files.exists(tempDir)) {
                Files.createDirectories(tempDir);
            }

            // 保存分片文件
            String chunkFileName = "chunk_" + partNumber;
            Path chunkPath = tempDir.resolve(chunkFileName);

            // 保存分片数据
            Files.copy(inputStream, chunkPath, StandardCopyOption.REPLACE_EXISTING);

            // 本地存储返回假ETag（因为本地存储在完成时会合并文件）
            String etag = "etag-" + partNumber;
            log.debug("本地分片保存成功: uploadId={}, partNumber={}, chunkPath={}, etag={}",
                    uploadId, partNumber, chunkPath, etag);

            return etag;
        } catch (Exception e) {
            log.error("本地分片保存失败: uploadId={}, partNumber={}, error={}", uploadId, partNumber, e.getMessage(), e);
            throw new ServiceException("本地分片保存失败: " + e.getMessage());
        }
    }

    @Override
    public boolean completeMultipartUpload(String fileName, String uploadId,
                                           java.util.List<com.ruoyi.file.domain.dto.CompleteMultipartRequest.PartInfo> parts, SysStorageConfig config) {
        try {
            // 临时目录路径
            String tempDirPath = RuoYiConfig.getProfile() + File.separator + "temp" + File.separator + uploadId;
            Path tempDir = Paths.get(tempDirPath);

            if (!Files.exists(tempDir)) {
                throw new ServiceException("临时目录不存在: " + tempDirPath);
            }

            // 确定最终文件路径 - 直接使用传入的fileName，因为在控制器层已经处理了路径生成
            Path finalFilePath = getFullPath(config, fileName);
            Files.createDirectories(finalFilePath.getParent());

            // 合并分片文件
            try (var outputStream = Files.newOutputStream(finalFilePath)) {
                // 按分片号排序
                parts.stream()
                        .sorted(Comparator.comparingInt(CompleteMultipartRequest.PartInfo::getPartNumber))
                        .forEach(part -> {
                            try {
                                Path chunkPath = tempDir.resolve("chunk_" + part.getPartNumber());
                                if (Files.exists(chunkPath)) {
                                    Files.copy(chunkPath, outputStream);
                                    log.debug("合并分片: {}", chunkPath);
                                } else {
                                    throw new ServiceException("分片文件缺失: chunk_" + part.getPartNumber());
                                }
                            } catch (Exception e) {
                                throw new RuntimeException("合并分片失败: " + e.getMessage(), e);
                            }
                        });
            }

            // 清理临时文件
            cleanupTempFiles(tempDirPath);

            log.info("本地分片上传完成: fileName={}, uploadId={}, finalPath={}", fileName, uploadId, finalFilePath);
            return true;
        } catch (Exception e) {
            log.error("完成本地分片上传失败: {}", e.getMessage(), e);
            throw new ServiceException("完成分片上传失败: " + e.getMessage());
        }
    }

    @Override
    public boolean abortMultipartUpload(String fileName, String uploadId, SysStorageConfig config) {
        try {
            // 清理临时文件
            String tempDirPath = RuoYiConfig.getProfile() + File.separator + "temp" + File.separator + uploadId;
            cleanupTempFiles(tempDirPath);

            log.info("取消本地分片上传成功: fileName={}, uploadId={}", fileName, uploadId);
            return true;
        } catch (Exception e) {
            log.error("取消本地分片上传失败: {}", e.getMessage(), e);
            throw new ServiceException("取消分片上传失败: " + e.getMessage());
        }
    }

    /**
     * 生成上传ID
     */
    private String generateUploadId() {
        return java.util.UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 清理临时文件
     */
    private void cleanupTempFiles(String tempDirPath) {
        try {
            Path tempDir = Paths.get(tempDirPath);
            if (Files.exists(tempDir)) {
                Files.walk(tempDir)
                        .sorted((a, b) -> b.compareTo(a)) // 先删除文件，再删除目录
                        .forEach(path -> {
                            try {
                                Files.deleteIfExists(path);
                                log.debug("删除临时文件: {}", path);
                            } catch (Exception e) {
                                log.warn("删除临时文件失败: {}", path, e);
                            }
                        });
            }
        } catch (Exception e) {
            log.error("清理临时文件失败: {}", tempDirPath, e);
        }
    }
}
